/****
 * AOC starter
 * Copyright (c) 2010, 02nz
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 * Latest version aviliable through the world-wide-web at the following url:
 * http://code.google.com/p/aoc-starter/
 ****/

#include <windows.h>
#include "_patcher.h"
#include <nt/ntdll.h>
#include "aoc.h"

//-----------------------------------------------------------------------

PWSTR _cdecl Error(PCWSTR Fmt, ...)
{
	ULONG ErrorCode = GetLastError();

	PWSTR Tmp = new WCHAR [512];
	PWSTR Pos = Tmp;

	if(Fmt)
	{
		va_list	argptr;
		va_start(argptr, Fmt);
		Pos += wvsprintf(Tmp, Fmt, argptr);
		va_end(argptr);
		lstrcpy(Pos, L"\n\n");
		Pos += 2;
	}

	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, ErrorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_SYS_DEFAULT), Pos, 511-(Pos-Tmp), NULL);
	return Tmp;
}

PWSTR _lstrdup(PCWSTR c)
{
	if(!c)
		return 0;
	LONG Len = lstrlen(c);
	PWSTR p = new WCHAR [Len+1];
	memcpy(p, c, Len << 1);
	p[Len] = 0;
	return p;
}

PWSTR _lstrdup(PCSTR c)
{
	if(!c)
		return 0;
	LONG Len = lstrlenA(c);
	PWSTR p = new WCHAR [Len+1];
	p[Len] = 0;
	for(int i = 0; i < Len; i++)
		p[i] = c[i];
	return p;
}

INT _lstricmp(PCWSTR dst, PCSTR src)
{
	if(!dst || !src)
		return -1;

	int f, l;
	do
	{
		if((f = *(dst++)) >= 'A' && f <= 'Z')
			f -= 'A' - 'a';
		if((l = *(src++)) >= 'A' && l <= 'Z')
			l -= 'A' - 'a';
	}
	while(f && f == l);

	return f - l;
}

///////////////////////////////////////////////////////////////////////////////////////

class cPROC
{
public:
	PROCESS_INFORMATION pi;
	STARTUPINFO si;
	PWSTR	Err;
	PUCHAR	Base;
	ULONG	Buff_Pos, Buff_Size;
	PUCHAR	Buff;

	cPROC(PCWSTR Path, PWSTR Args, ULONG Size)
		:	Err(0)
		,	Buff_Size(Size)
		,	Buff(0)
	{
		PROCESS_BASIC_INFORMATION BasicInfo;
		PEB Peb;

		ZeroMemory(&pi, sizeof PROCESS_INFORMATION);
		ZeroMemory(&si, sizeof STARTUPINFO);
		si.cb = sizeof(si);
		si.dwFlags = STARTF_USESHOWWINDOW;
		si.wShowWindow = SW_NORMAL;

		WCHAR Dir[MAX_PATH];
		PWSTR e = &Dir[ lstrlen(Path) ];
		memcpy(Dir, Path, (PUCHAR)e - (PUCHAR)Dir);
		while(e >= Dir && *e != '\\' && *e != '/')
			--e;
		e[1] = 0;

		if(!CreateProcess(Path, Args, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, Dir, &si, &pi))
			Err = Error(L"CreateProcess");
		else
		if(NT_ERROR(NtQueryInformationProcess(pi.hProcess, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL)))
			Err = Error(L"QueryInformationProcess");
		else
		if(!ReadProcessMemory(pi.hProcess, BasicInfo.PebBaseAddress, &Peb, sizeof(PEB), NULL))
			Err = Error(L"ReadProcessMemory");
		
		Base = (PUCHAR)Peb.ImageBaseAddress;
	}
	~cPROC()
	{
		if(Err)
			TerminateProcess(pi.hProcess, 0);
		else
			ResumeThread(pi.hThread);

		CloseHandle(pi.hThread);
		CloseHandle(pi.hProcess);
	}
	inline PVOID GetPtr(PVOID Base, ULONG Offset)
	{
		PVOID ptr = NULL;
		if(NT_SUCCESS(NtReadVirtualMemory(pi.hProcess, (PUCHAR)Base + Offset, &ptr, sizeof(ptr), 0)))
			return ptr;
		return NULL;
	}
	PVOID GetModuleBaseEx(LPCSTR Name)
	{
		PROCESS_BASIC_INFORMATION BasicInformation;
		LONG Status;

		Status = NtQueryInformationProcess(pi.hProcess,
						ProcessBasicInformation,
						&BasicInformation,
						sizeof(PROCESS_BASIC_INFORMATION),
						0 );
		if(!NT_SUCCESS(Status))
			return NULL;

		PPEB_LDR_DATA pLdr = (PPEB_LDR_DATA)GetPtr(BasicInformation.PebBaseAddress, OFFSETOF(PEB, Ldr));
		PLIST_ENTRY pMark = (PLIST_ENTRY)GetPtr(pLdr, OFFSETOF(PEB_LDR_DATA, InMemoryOrderModuleList));

		if(!pLdr || !pMark)
			return NULL;

		ULONG Try = 0;

		for(PLIST_ENTRY pEntry = (PLIST_ENTRY)GetPtr(pMark, OFFSETOF(LIST_ENTRY, Flink));
			pEntry && pEntry != pMark;
			pEntry = (PLIST_ENTRY)GetPtr(pEntry, OFFSETOF(LIST_ENTRY, Flink))) 
		{
			if(Try++ > 0xFF)
				break;

			PLDR_DATA_TABLE_ENTRY pModule = CONTAINING_RECORD(pEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderModuleList);

			UNICODE_STRING BaseDllName;
			WCHAR Buffer[48];

			Status = NtReadVirtualMemory(
						pi.hProcess,
						(PUCHAR)pModule + OFFSETOF(LDR_DATA_TABLE_ENTRY, BaseDllName),
						&BaseDllName,
						sizeof(BaseDllName),
						0 );
			if(!NT_SUCCESS(Status))
				continue;

			USHORT Bytes = Min<USHORT>(BaseDllName.MaximumLength, sizeof(Buffer)-sizeof(Buffer[0]));
			Status = NtReadVirtualMemory(
						pi.hProcess,
						BaseDllName.Buffer,
						&Buffer,
						Bytes,
						0 );
			if(!NT_SUCCESS(Status))
				continue;

			Buffer[Bytes>>1] = 0;

			if(!_lstricmp(Buffer, Name))
				return GetPtr(pModule, OFFSETOF(LDR_DATA_TABLE_ENTRY, DllBase));
		}
		return NULL;
	}
	VOID WriteMemory(PVOID Ptr, PVOID Buff, ULONG Size)
	{
		if(Err)
			return;

		ULONG Attrib = 0;
		VirtualProtectEx(pi.hProcess, Ptr, Size, PAGE_EXECUTE_READWRITE, &Attrib);

		ULONG Bytes = 0;
		if(!WriteProcessMemory(pi.hProcess, Ptr, Buff, Size, &Bytes))
		{
			Err = Error(L"WriteProcessMemory");
		}
		else
		{
			FlushInstructionCache(pi.hProcess, Ptr, Size);
			VirtualProtectEx(pi.hProcess, Ptr, Size, Attrib, &Attrib);
		}
	}
	PUCHAR AllocateMemory(ULONG Size)
	{
		if(!Buff || Buff_Pos + Size > Buff_Size)
		{
			Buff = (PUCHAR)VirtualAllocEx(pi.hProcess, 0, Buff_Size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
			Buff_Pos = 0;

			if(!Buff)
				Err = Error(L"VirtualAllocEx");
		}
		PUCHAR Ret = &Buff[Buff_Pos];
		Buff_Pos += Size + (4 - (Size & 3));
		return Ret;
	}
};

///////////////////////////////////////////////////////////////////////////////////////

interface cHookBase // easy hooker :p
{
public:
	PWSTR *	_Err;
	cLIB *	_Lib;
	cPROC *	_Proc;
	
	cHookBase(cLIB *Lib, cPROC *Proc)
		:	_Lib(Lib)
		,	_Proc(Proc)
	{
		_Err = &Proc->Err;
	}

public:
	virtual PWSTR	Check() = 0;
	virtual PWSTR	Prepare() = 0;
	virtual PWSTR	Apply() = 0;
	virtual VOID	Release() = 0;

public:
	PUCHAR AllocArgFn(PUCHAR pHK, ULONG ArgSize, ULONG FuncOffset, PUCHAR &_Buff, PUCHAR _Jmp, PUCHAR &_BuffEx)
	{
		PUCHAR pFuncEx = &_Proc->Base[FuncOffset];

		ULONG Size = 0;
		while(*(PULONG)&pHK[Size] != 0xDEADBABE)
			Size++;

		ULONG Buff_Size = Size + 16 + ArgSize;

		_Buff = new UCHAR[Buff_Size];
		PUCHAR Pos = _Buff;

		memcpy(Pos, pHK, Size);
		Pos += Size;

		_BuffEx = _Proc->AllocateMemory(Buff_Size);
		if(!_BuffEx)
			return 0;

	// 55        push    ebp
	// 8B EC     mov     ebp, esp
	// 83 E4 F8  and     esp, 0FFFFFFF8h
		PUCHAR pFuncLocal = &_Lib->pBase[FuncOffset];
		memcpy(Pos, &pFuncLocal[1+2], 3);
		Pos += 3;

		Pos[0] = 0xE9;
		*(PDWORD)&Pos[1] = &pFuncEx[1+2+3] - (&_BuffEx[ Pos - _Buff ] + 5);
		Pos += 5;

		_Jmp[0] = 0xE9;
		*(PDWORD)&_Jmp[1] = _BuffEx - (pFuncEx + 5);

	//------------------------------
		ULONG Align = 8 - ((ULONG)(Pos) & 7);
		memset(Pos, 0x90, Align), Pos += Align;

		for(ULONG i = 0; i < Size; i++)
			if( *(PULONG)&_Buff[i] == 0x1337BEEF )
				*(PVOID *)&_Buff[i] = (PVOID) &_BuffEx[ Pos - _Buff ];

		return Pos;
	}

};

///////////////////////////////////////////////////////////////////////////////////////

ULONG FindCheckDebuggerFunc(PUCHAR pBase, PUCHAR pBaseMax, PUCHAR pBaseEx)
{
	PUCHAR pFound = patcher::FindStringPtr(pBase, pBaseMax, "HttpPatchFolder", 1);
	if(!pFound)
		return NULL;

	pFound = patcher::FindPushPtr(pBase, pBaseMax, pBaseEx + (pFound - pBase));
	if(!pFound)
		return NULL;

//.text:00401E92 FF 15 18 A4 69 01                    call    ds:IsDebuggerPresent
//.text:00401E98 85 C0                                test    eax, eax
//.text:00401E9A 0F 85 B1 03 00 00                    jnz     loc_402251

	pFound = patcher::FindPatternBack(pFound, pFound-64,
		"\xFF\x15\x18\xA4\x69\x01\x85\xC0\x0F\x85",
		"xx????xxx");
	if(!pFound)
		return NULL;

	return (&pFound[6+2] - pBase);
}

interface cHookDbg : public cHookBase
{
protected:
	ULONG	_Ptr;
public:
	cHookDbg(cLIB *Lib, cPROC *Proc)
		: cHookBase(Lib, Proc)
	{
		if(!(_Ptr = FindCheckDebuggerFunc(Lib->pBase, &Lib->pBase[Lib->ImageSize], (PUCHAR)Lib->ImageBase)))
			*_Err = _lstrdup(L"Dbg: Not found");
	}
	PWSTR Check()
	{
		return *_Err;
	}
	PWSTR Prepare()
	{
		return NULL;
	}
	PWSTR Apply()
	{
		if(*_Err)
			return *_Err;

		_Proc->WriteMemory(&_Proc->Base[_Ptr], "\x90\xE9", 2);
		return _Proc->Err;
	}
	VOID Release()
	{
		delete this;
	}
};

///////////////////////////////////////////////////////////////////////////////////////

ULONG FindNamedFunc(PCSTR Name, bool FullName, PUCHAR pBase, PUCHAR pBaseMax, PUCHAR pBaseEx)
{
	PUCHAR pFound = patcher::FindStringPtr(pBase, pBaseMax, Name, FullName);
	if(!pFound)
		return NULL;

	pFound = patcher::FindPushPtr(pBase, pBaseMax, pBaseEx + (pFound - pBase));
	if(!pFound)
		return NULL;

	PUCHAR pFn = patcher::FindEpilogFrom(pFound, 0x1000);
	if(!pFn)
		return NULL;

	if(patcher::FindPattern(pFn, pFound, "\xCC\xCC\xCC", "xxx"))
		return NULL;

	return (pFn - pBase);
}

#pragma pack(push, 1)
typedef struct STDSTR
{
	ULONG u;
	union
	{
		UCHAR	Buf[16];
		PUCHAR	Ptr;
	} V;
	ULONG Size;
	ULONG Reserved;
} *PSTDSTR;
#pragma pack(pop)

typedef struct TARG_LOGIN
{
//	PUCHAR	Lang; // PSTDSTR Lang = (PSTDSTR) &Arg->Lang[0x3C];
	STDSTR	Region;
} *PARG_LOGIN;

void LoginCharacterHook(PVOID, PVOID, PSTDSTR);

interface cHookLogin : public cHookBase
{
protected:
	ULONG	_VerifyLanguageSettingPtr;
	ULONG	_LoginCharacterPtr;
	UCHAR	_Jmp[5];
	PUCHAR	_Buff;
	LONG	_BuffSize;
	PUCHAR	_BuffEx;
	PCSTR	_Region;
public:
	cHookLogin(cLIB *Lib, cPROC *Proc, PCSTR Region)
		:	cHookBase(Lib, Proc)
		,	_Region(Region)
		,	_Buff(0)
	{
		_VerifyLanguageSettingPtr = FindNamedFunc(
			"PlayerAgentProxy_t::VerifyLanguageSetting(",
			0,
			Lib->pBase,
			&Lib->pBase[Lib->ImageSize], (PUCHAR)Lib->ImageBase);

		_LoginCharacterPtr = FindNamedFunc(
			"PlayerAgentProxy_t::LoginCharacter(",
			0,
			Lib->pBase,
			&Lib->pBase[Lib->ImageSize], (PUCHAR)Lib->ImageBase);

		if(!_VerifyLanguageSettingPtr || !_LoginCharacterPtr)
			*_Err = _lstrdup(L"Login: Not found");
	}
	PWSTR Check()
	{
		if(*_Err)
			return *_Err;

		return ( (&_Lib->pBase[_LoginCharacterPtr]) [1+2] == 0x83)
			? NULL
			: (*_Err = _lstrdup(L"Login: Incompatible EXE!"));
	}
	PWSTR Prepare()
	{
		if(*_Err)
			return *_Err;

		PUCHAR ArgLocal = AllocArgFn((PUCHAR)LoginCharacterHook, sizeof TARG_LOGIN,
			_LoginCharacterPtr, _Buff, _Jmp, _BuffEx);

		if(*_Err)
			return *_Err;

//------------------------------
		PARG_LOGIN pArg = (PARG_LOGIN) ArgLocal;
		pArg->Region.Size = 0x02;
		memcpy(&pArg->Region.V.Buf, _Region, 2);
		pArg->Region.Reserved = 0x0F;

		ArgLocal += sizeof TARG_LOGIN;
//------------------------------

		_BuffSize = ArgLocal - _Buff;
		return NULL;
	}
	PWSTR Apply()
	{
		if(*_Err)
			return *_Err;

		_Proc->WriteMemory(&_Proc->Base[_VerifyLanguageSettingPtr], "\xC2\x04\x00", 3);

		_Proc->WriteMemory(&_Proc->Base[_LoginCharacterPtr], _Jmp, 5);
		_Proc->WriteMemory(_BuffEx, _Buff, _BuffSize);

		return *_Err;
	}
	VOID Release()
	{
		SAFE_DELETE_ARRAY(_Buff);
		delete this;
	}
};

///////////////////////////////////////////////////////////////////////////////////////

interface cHookLock : public cHookBase
{
protected:
	ULONG	_HookOffs;

public:
	cHookLock(cLIB *Lib, cPROC *Proc)
		:	cHookBase(Lib, Proc)
		,	_HookOffs(0)
	{
		PUCHAR pFound = patcher::FindStringPtr(
			Lib->pBase, &Lib->pBase[Lib->ImageSize],
			"Lock2.txt", 1);
		if(!pFound)
			return;
		_HookOffs = (pFound - Lib->pBase);
	}
	PWSTR Check()
	{
		if(*_Err)
			return *_Err;
		if(!_HookOffs)
			*_Err = _lstrdup("!lock");
		if(*_Err)
			return *_Err;
		return NULL;
	}
	PWSTR Prepare()
	{
		if(*_Err)
			return *_Err;

		return NULL;
	}
	PWSTR Apply()
	{
		if(*_Err)
			return *_Err;

		UCHAR c = 'a' + (GetTickCount() & 0xFFFF) % 16;
		UCHAR i = ((GetTickCount() << 3) & 0xFFF) % 3;

		_Proc->WriteMemory(&_Proc->Base[_HookOffs+i], &c, 1);
		return *_Err;
	}
	VOID Release()
	{
		delete this;
	}
};

///////////////////////////////////////////////////////////////////////////////////////

ULONG FindJoinMinigameFunc(PUCHAR pBase, PUCHAR pBaseMax, PUCHAR pBaseEx)
{
	return FindNamedFunc("JoinMinigame", 1, pBase, pBaseMax, pBaseEx);
}

void JoinMinigameHook(PVOID, PVOID);

typedef struct TARG_JMG
{
	BOOL (WINAPI *_PlaySoundW)(LPCWSTR, HMODULE, DWORD);
	BOOL PlaySoundOnce;
	WCHAR Path[MAX_PATH];
} *PARG_JMG;

interface cHookJoinMinigame : public cHookBase
{
protected:
	ULONG	_JoinMinigamePtr;
	UCHAR	_Jmp[5];
	PUCHAR	_Buff;
	LONG	_BuffSize;
	PUCHAR	_BuffEx;
	PCWSTR	_Sound;
	BOOL	_PlaySoundOnce;

public:
	cHookJoinMinigame(cLIB *Lib, cPROC *Proc, PCWSTR Sound, BOOL PlaySoundOnce)
		:	cHookBase(Lib, Proc)
		,	_Sound(Sound)
		,	_PlaySoundOnce(PlaySoundOnce)
		,	_JoinMinigamePtr(0)
	{
		if(!Sound || GetFileAttributes(Sound) == ~0)
			return;

		if(!(_JoinMinigamePtr = FindJoinMinigameFunc(Lib->pBase, &Lib->pBase[Lib->ImageSize], (PUCHAR)Lib->ImageBase)))
			*_Err = _lstrdup(L"JoinMinigame: Not found");
	}
	PWSTR Check()
	{
		if(!_JoinMinigamePtr)
			return NULL;

		if(*_Err)
			return *_Err;

		return ( (&_Lib->pBase[_JoinMinigamePtr]) [1+2] == 0x83)
			? NULL
			: (*_Err = _lstrdup(L"JoinMinigame: Incompatible EXE!"));
	}
	PWSTR Prepare()
	{
		if(!_JoinMinigamePtr)
			return NULL;

		if(*_Err)
			return *_Err;

		PUCHAR ArgLocal = AllocArgFn((PUCHAR)JoinMinigameHook, sizeof TARG_JMG,
			_JoinMinigamePtr, _Buff, _Jmp, _BuffEx);

		if(*_Err)
			return *_Err;

//------------------------------
		PARG_JMG pArg = (PARG_JMG) ArgLocal;
		PUCHAR pWmm = 0;

// Y.this is lame! O_o
		SetThreadPriority(NtCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
		SetThreadPriority(_Proc->pi.hThread, THREAD_PRIORITY_LOWEST);

		ULONG Mask = 0, Tmp;
		GetProcessAffinityMask(_Proc->pi.hProcess, &Mask, &Tmp);
		Mask &= ~1;
		if(Mask)
			SetProcessAffinityMask(_Proc->pi.hProcess, 1);

		ResumeThread(_Proc->pi.hThread);
		Sleep(0);

		for(ULONG Try = 0; Try < 500; Try++)
		{
			if(pWmm = (PUCHAR) _Proc->GetModuleBaseEx("WINMM.DLL"))
				break;
			Sleep(10);
		}

		SuspendThread(_Proc->pi.hThread);

		if((Mask >> 3) & 1)
			SetProcessAffinityMask(_Proc->pi.hProcess, Mask);
		else
			SetProcessAffinityMask(_Proc->pi.hProcess, Mask | 1);

		SetThreadPriority(_Proc->pi.hThread, THREAD_PRIORITY_NORMAL);
		SetThreadPriority(NtCurrentThread(), THREAD_PRIORITY_NORMAL);

		if(!pWmm)
			return (*_Err = _lstrdup(L"!WINMM.DLL"));

		extern ULONG gPlaySoundW;
		*(PUCHAR *) &pArg->_PlaySoundW = &pWmm[gPlaySoundW];
		lstrcpy(pArg->Path, _Sound);
		pArg->PlaySoundOnce = _PlaySoundOnce;

		ArgLocal += sizeof TARG_JMG;
//------------------------------

		_BuffSize = ArgLocal - _Buff;
		return NULL;
	}
	PWSTR Apply()
	{
		if(!_JoinMinigamePtr)
			return NULL;

		if(*_Err)
			return *_Err;

		_Proc->WriteMemory(&_Proc->Base[_JoinMinigamePtr], _Jmp, 5);
		_Proc->WriteMemory(_BuffEx, _Buff, _BuffSize);

		return *_Err;
	}
	VOID Release()
	{
		SAFE_DELETE_ARRAY(_Buff);
		delete this;
	}
};

///////////////////////////////////////////////////////////////////////////////////////

PWSTR c0(cHookBase *p){ return p->Check(); }
PWSTR c1(cHookBase *p){ return p->Prepare(); }
PWSTR c2(cHookBase *p){ return p->Apply(); }

class cHookCaller
{
	cHookBase **_Hooks;
	UINT		_HooksCount;

public:
	cHookCaller(cHookBase **Hooks, UINT HooksCount)
		: _Hooks(Hooks)
		, _HooksCount(HooksCount)
	{
	}
	~cHookCaller()
	{
		for(UINT i = 0; i < _HooksCount; i++)
			_Hooks[i]->Release();
	}
	PWSTR Go(BOOL BreakOnError)
	{
		PWSTR (*a[])(cHookBase *) =
		{
			c0,
			c1,
			c2,
		};
		PWSTR e = NULL;

		for(ULONG i = 0; i < _HooksCount; i++)
			for(ULONG j = 0; j < ARRAY_COUNT(a); j++)
				if((e = a[j](_Hooks[i])) && BreakOnError)
					return e;

		return NULL;
	}
};

PWSTR Run(PCWSTR Path, PWSTR Args, PCSTR Region, PCWSTR Sound, BOOL PlaySoundOnce, BOOL BreakOnError, BOOL bTSW)
{
	cLIB Lib(Path);

	if(!Lib.pBase)
		return Error(L"LoadLibraryEx");

	cPROC Proc(Path, Args, 0x1000);

	if(Proc.Err)
		return Proc.Err;

	if(bTSW)
	{
		cHookBase *Hooks[] =
		{
			new cHookDbg(&Lib, &Proc),
			new cHookLock(&Lib, &Proc),
		};

		cHookCaller hc(Hooks, ARRAY_COUNT(Hooks));
		PWSTR Err = hc.Go(BreakOnError);
		return Err;
	}

	cHookBase *Hooks[] =
	{
		new cHookDbg(&Lib, &Proc),
		new cHookLogin(&Lib, &Proc, Region),
		new cHookJoinMinigame(&Lib, &Proc, Sound, PlaySoundOnce),
		new cHookLock(&Lib, &Proc),
	};

	cHookCaller hc(Hooks, ARRAY_COUNT(Hooks));
	PWSTR Err = hc.Go(BreakOnError);
	return Err;
}

#pragma optimize("", off)

__declspec(naked, noinline)
void LoginCharacterHook(PVOID, PVOID, PSTDSTR ClientLanguage)
{
	_asm
	{
		push ebp
		mov ebp, esp
		sub esp, __LOCAL_SIZE
		pushad
	}

	PARG_LOGIN pArg;
	pArg = (PARG_LOGIN)0x1337BEEF;

	if(ClientLanguage)
		ClientLanguage = &pArg->Region;

	_asm
	{
		popad
		add esp, __LOCAL_SIZE
		_emit 0xBE
		_emit 0xBA
		_emit 0xAD
		_emit 0xDE
	}
}

__declspec(naked, noinline)
void JoinMinigameHook(/*FLOAT */PVOID Time, PVOID Wtf)
{
	_asm
	{
		push ebp
		mov ebp, esp
		sub esp, __LOCAL_SIZE
		pushad
	}

	PARG_JMG pArg;
	pArg = (PARG_JMG)0x1337BEEF;

	if(pArg->_PlaySoundW && (!pArg->PlaySoundOnce || !(Time || Wtf)))
	{
		pArg->_PlaySoundW(0, 0, 0);
		pArg->_PlaySoundW(pArg->Path, 0, SND_FILENAME|SND_ASYNC);
	}

	_asm
	{
		popad
		add esp, __LOCAL_SIZE
		_emit 0xBE
		_emit 0xBA
		_emit 0xAD
		_emit 0xDE
	}
}

